#include "mouse.h"

using namespace Epsilon;

#include <cstring>  // memset (...)
#include "../STL.h" // eSTL::IterValid

Epsilon::Mouse::Mouse (void)
{
  Reset ();
}

void
Epsilon::Mouse::Reset (void)
{
        x_ = 0;
  delta_x_ = 0;

        y_ = 0;
  delta_y_ = 0;

  /* No buttons are pressed initially... */
  memset (&buttons_, 0, sizeof (bool) * Buttons::_NUMBER_OF_BUTTONS);
}


bool
Mouse::InstallListener (Mouse_Listener* pListener)
{
  if (pListener != NULL) {
    listeners_.push_back (pListener);
    return true;
  } else {
    return false;
  }
}

bool
Mouse::UninstallListener (Mouse_Listener* pListener)
{
  bool removed = false;

  if (pListener != NULL) {
    std::vector <Mouse_Listener *>::iterator listener =
        listeners_.begin ();
    std::vector <Mouse_Listener *>::iterator end =
        listeners_.end ();

    while (eSTL::IterValid (listener, end)) {
      if ((*listener) == pListener) {
        listeners_.erase (listener);
        removed = true;
      }

      ++listener;
    }
  }

  return removed;
}


// Silly trick for gcc's inferrior C pre-processor...
#define CALL_LISTENER(__mem_func) __mem_func

#define NOTIFY_LISTENERS(event_handler__)                     \
  std::vector <Mouse_Listener *>::const_iterator listener__ = \
    listeners_.begin ();                                      \
  std::vector <Mouse_Listener *>::const_iterator end__      = \
    listeners_.end   ();                                      \
                                                              \
  /* Run through all listeners, and notify them of            \
       this event... */                                       \
  while (eSTL::IterValid (listener__, end__)) {               \
    /* Allow the listener to prevent delegation... */         \
    if ((*listener__)->CALL_LISTENER(event_handler__))        \
      return;                                                 \
                                                              \
    ++listener__;                                             \
  }


void
Mouse::PressButton (Buttons::Button button)
{
  //assert ( button < Buttons::_NUMBER_OF_BUTTONS &&
  //         buttons_ [button] == false );
  buttons_ [button] = true;

  NOTIFY_LISTENERS (OnButtonPress (button));
}

void
Mouse::ReleaseButton (Buttons::Button button) {
  //assert ( button < Buttons::_NUMBER_OF_BUTTONS &&
  //         buttons_ [button] == false );
  buttons_ [button] = false;

  NOTIFY_LISTENERS (OnButtonRelease (button));
}

void 
Mouse::MoveCursor ( int  x,
                    int  y,
                    bool notify )
{
  delta_x_ = x - x_;
  delta_y_ = y - y_;

  x_ = x;
  y_ = y;

  if (notify) {
    NOTIFY_LISTENERS (OnMouseMove (x, y));
  }
}

void
Mouse::MoveCursorDelta ( int  delta_x,
                         int  delta_y,
                         bool notify )
{
  delta_x_ = delta_x;
  delta_y_ = delta_y;

  x_ += delta_x;
  y_ += delta_y;

  if (notify) {
    NOTIFY_LISTENERS (OnMouseDelta (delta_x, delta_y));
  }
}

/* TODO: Can we find a solution that works given only absolute
           coordinates (x,y) or delta (delta_x,delta_y)? */
void
Mouse::MoveCursorEx ( int x,
                      int y,
                      int delta_x,
                      int delta_y )
{
  MoveCursor       (x,       y,       false);
  MoveCursorDelta  (delta_x, delta_y, false);

  NOTIFY_LISTENERS (OnMouseMove (x, y));
}
